using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace BouncinBall.Components
{
    public class FieldManager : Microsoft.Xna.Framework.GameComponent
    {
        #region Constants

        public const float BALL_DIAMETER = 0.03F;
        public const float PADDLE_DIAMETER = 0.08F;
        public const float POINT_LIGHT_HEIGHT = 10F;

        #endregion Constants

        #region Constructors

        public FieldManager(Game game, float gravity)
            : base(game)
        {
            _grav = gravity;
        }

        #endregion Constructors

        #region Properties

        public Components.Ball Ball
        {
            get { return _ball; }
        }
        private Components.Ball _ball;

        public float Gravity { get { return _grav; } } //m/s^2
        private float _grav;

        public Components.Paddle Paddle
        {
            get { return _paddle; }
        }
        private Components.Paddle _paddle;

        private Components.Shadow _shadow;

        //private bool _hitLastCheck;

        private Indicators.LocatorArrow _leftArrow;
        private Indicators.LocatorArrow _rightArrow;
        private Indicators.LocatorArrow _upArrow;
        private Indicators.LocatorArrow _downArrow;

        #endregion Properties

        #region Overrides

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            // TODO: Add your initialization code here

            base.Initialize();
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            // TODO: Add your update code here
            _ball.Visible = IsBallVisible;
            if (_ball.Visible)
            {
                //Check for hit
                if (IsHit)
                {
                    BallHit(new BallHitEventArgs(0F, _ball.Velocity.Magnitude, _paddle.Velocity.Magnitude, false, false)); //fire before applying hit

                    ////Reset field coordinates
                    //_paddle.Position = new Utility.Position();
                    //_ball.Position = _paddle.Position + new Utility.Position(0, 0, (Components.Paddle.PADDLE_THICKNESS + _ball.Diameter) / 2.0F);
                    
                    switch (((BouncingBallGame)Game).Mode)
                    {
                        case BouncingBallGame.GameMode.Normal:
                            _ball.Hit(_paddle.Velocity, _paddle.OrientationNormalVector);
                            break;
                        case BouncingBallGame.GameMode.DebugVerticalBounceFixed:
                        case BouncingBallGame.GameMode.DebugVerticalBounceFree:
                        case BouncingBallGame.GameMode.DebugLateralBounce:
                            _ball.Hit(_paddle.Velocity, new Vector3(0, 0, 1));
                            break;
                        default:
                            //case BouncingBallGame.GameMode.DebugDropFarSouth:
                            //case BouncingBallGame.GameMode.DebugDrop:
                            //case BouncingBallGame.GameMode.DebugDropFarWestNearNorth:
                            //case BouncingBallGame.GameMode.DebugDropMiddleEast:
                            //case BouncingBallGame.GameMode.DebugDropNearNorth:
                            //case BouncingBallGame.GameMode.DebugDropFarSouth:
                            //case BouncingBallGame.GameMode.DebugShadowTest:
                            //don't hit
                            break;
                    }
                }

                _ball.SetScreenPosition(_ball.Position, _paddle.Position);
            }
            //else  //give ball position to each arrow
            //{
            Vector2 ballRelPos = new Vector2();
            ballRelPos = _ball.Position.LateralPosition - _paddle.Position.LateralPosition;
            _leftArrow.SetBallRelativePosition(ballRelPos);
            _rightArrow.SetBallRelativePosition(ballRelPos);
            _upArrow.SetBallRelativePosition(ballRelPos);
            _downArrow.SetBallRelativePosition(ballRelPos);
            //}

            _shadow.Visible = IsShadowVisible;
            if (_shadow.Visible)
            {
                _shadow.Position = new Utility.Position(_ball.Position.LateralPosition.X, _ball.Position.LateralPosition.Y, 0);
                _shadow.Diameter = (_ball.Position.Height < POINT_LIGHT_HEIGHT) ? _ball.Diameter * POINT_LIGHT_HEIGHT / (POINT_LIGHT_HEIGHT - _ball.Position.Height) : 0F;

                _shadow.SetScreenPosition(_shadow.Position, _paddle.Position);
            }

            _paddle.Depth = (_ball.Position.Height > _paddle.Position.Height) ? BouncingBallGame.PADDLE_DEPTH : BouncingBallGame.PADDLE_DEPTH_OVER; //put paddle over ball

            //Paddle always visible, no position change necessary
            //_paddle.SetScreenPosition(_paddle.Position, _paddle.Position);

            base.Update(gameTime);
        }

        #endregion Overrides

        #region Public Methods

        public void SetComponents(Components.Ball ball, Components.Paddle paddle, Components.Shadow shadow, Indicators.LocatorArrow left, Indicators.LocatorArrow right, Indicators.LocatorArrow up, Indicators.LocatorArrow down)
        {
            _ball = ball;
            _paddle = paddle;
            _shadow = shadow;
            _leftArrow = left;
            _rightArrow = right;
            _upArrow = up;
            _downArrow = down;
        }

        public void StartGame(float gravity)
        {
            _grav = gravity;

            //_paddle.Position = new Utility.Position(0, 0, 0); //initialized here by default
            switch (((BouncingBallGame)Game).Mode)
            {
                case BouncingBallGame.GameMode.DebugDrop:
                case BouncingBallGame.GameMode.DebugVerticalBounceFixed:
                    //use position from touch (assigned in BouncinBallGame)
                    _ball.Velocity = new Utility.Velocity();
                    break;
                //case BouncingBallGame.GameMode.DebugDropFarSouth:
                //    _ball.Position = new Utility.Position(BouncinBall.BouncingBallGame.INIT_X_FARSOUTH, BouncinBall.BouncingBallGame.INIT_Y_FARSOUTH, BouncinBall.BouncingBallGame.INIT_Z_FARSOUTH);
                //    break;
                //case BouncingBallGame.GameMode.DebugDropFarWestNearNorth:
                //    _ball.Position = new Utility.Position(BouncinBall.BouncingBallGame.INIT_X_FARWESTNEARNORTH, BouncinBall.BouncingBallGame.INIT_Y_FARWESTNEARNORTH, BouncinBall.BouncingBallGame.INIT_Z_FARWESTNEARNORTH);
                //    break;
                //case BouncingBallGame.GameMode.DebugDropMiddleEast:
                //    _ball.Position = new Utility.Position(BouncinBall.BouncingBallGame.INIT_X_MIDDLEEAST, BouncinBall.BouncingBallGame.INIT_Y_MIDDLEEAST, BouncinBall.BouncingBallGame.INIT_Z_MIDDLEEAST);
                //    break;
                //case BouncingBallGame.GameMode.DebugDropNearNorth:
                //    _ball.Position = new Utility.Position(BouncinBall.BouncingBallGame.INIT_X_NEARNORTH, BouncinBall.BouncingBallGame.INIT_Y_NEARNORTH, BouncinBall.BouncingBallGame.INIT_Z_NEARNORTH);
                //    break;
                case BouncingBallGame.GameMode.DebugShadowTest:
                    //position so ball is just outside of viewing area, but should still be able to see shadow on screen
                    _ball.Position = new Utility.Position((_paddle.Diameter + _ball.Diameter) / 2, BouncinBall.BouncingBallGame.INIT_Y, BouncinBall.BouncingBallGame.INIT_Z);
                    _ball.Velocity = new Utility.Velocity();
                    break;
                case BouncingBallGame.GameMode.DebugLateralBounce:
                    //position so ball is just outside of viewing area, initial velocity
                    _ball.Position = new Utility.Position((_paddle.Diameter + _ball.Diameter) / 2, (_paddle.Diameter + _ball.Diameter) / 2, BouncinBall.BouncingBallGame.INIT_Z);
                    _ball.Velocity = new Utility.Velocity(-1 * BouncingBallGame.LATERAL_BOUNCE_VELOCITY, -1 * BouncingBallGame.LATERAL_BOUNCE_VELOCITY, 0.0F);
                    break;
                default:
                //case BouncingBallGame.GameMode.DebugVerticalBounceFree:
                //case BouncingBallGame.GameMode.Normal:
                    _ball.Position = new Utility.Position(BouncinBall.BouncingBallGame.INIT_X, BouncinBall.BouncingBallGame.INIT_Y, BouncinBall.BouncingBallGame.INIT_Z);
                    _ball.Velocity = new Utility.Velocity();
                    break;
            }
        }

        public void StopGame()
        {


            _grav = 0;
            _ball.Velocity = new Utility.Velocity();
        }

        public void SetArrowVisibility(bool visible)
        {
            _leftArrow.Visible = _rightArrow.Visible = _downArrow.Visible = _upArrow.Visible = visible;
        }

        #endregion Public Methods

        #region Private Methods

        private bool IsInVisibleMode
        {
            get
            {
                return ((BouncingBallGame)Game).Mode != BouncingBallGame.GameMode.PreGame;
            }
        }

        private bool IsBallVisible
        {
            get
            {
                return IsInVisibleMode && _ball.Position.Height < BouncingBallGame.BALL_VISIBLE_HEIGHT && IsBallInScreenBounds;
            }
        }

        private bool IsShadowVisible
        {
            get
            {
                return IsInVisibleMode //game needs to be in visible game mode
                    && _ball.Position.Height < POINT_LIGHT_HEIGHT //ball needs to be below light to cast shadow
                    && _ball.Position.Height > _paddle.Position.Height; //ball needs to be higher than paddle to cast shadow diameter > ball diameter
            }
        }

        private bool IsBallInScreenBounds
        {
            get
            {
                return _ball.Position.LateralPosition.X + _ball.Diameter > _paddle.Position.LateralPosition.X - _paddle.Diameter //lower x bound
                    && _ball.Position.LateralPosition.X - _ball.Diameter < _paddle.Position.LateralPosition.X + _paddle.Diameter //upper x bound
                    && _ball.Position.LateralPosition.Y + _ball.Diameter > _paddle.Position.LateralPosition.Y - _paddle.Diameter //lower Y bound
                    && _ball.Position.LateralPosition.Y - _ball.Diameter < _paddle.Position.LateralPosition.Y + _paddle.Diameter; //lower Y bound
            }
        }

        /// <summary>
        /// Returns if ball and paddle collide (intersect)
        /// <remarks>If collision detected on last "IsHit" check, will return false to allow ball time to clear paddle space</remarks>
        /// </summary>
        private bool IsHit
        {
            get
            {
                return /*_hitLastCheck = !_hitLastCheck && */_ball.BoundingShape.Intersects(_paddle.BoundingShape); //use XNA bounding shapes to determine collision
                //NOTE: this does not account for potential for fast moving user/ball to pass through inbetween frames
            }
        }

        #endregion Private Methods

        #region Events

        //http://geekswithblogs.net/nuri/archive/2007/02/14/106342.aspx
        public event BallHitEventHandler BallHit;

        public delegate void BallHitEventHandler(BallHitEventArgs evt);

        public class BallHitEventArgs : EventArgs
        {
            public float PaddleDisplacement { get; set; }
            public float BallSpeed { get; set; }
            public float PaddleSpeed { get; set; }
            public bool IsBallHighAngleIn { get; set; }
            public bool IsBallHighAngleOut { get; set; }

            public BallHitEventArgs(float paddleDisplacementSinceLastHit, float ballSpeed, float paddleSpeed, bool ballInHighAngle, bool ballOutHighAngle)
            {
                PaddleDisplacement = paddleDisplacementSinceLastHit;
                BallSpeed = ballSpeed;
                PaddleSpeed = paddleSpeed;
                IsBallHighAngleIn = ballInHighAngle;
                IsBallHighAngleOut = ballOutHighAngle;
            }
        }

        #endregion Events
    }
}
